﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.Data;


using DbSharp.Framework.DbObject;
using DbSharp.Framework.Common;


namespace DbSharp.Framework.CaseFile.Parser
{

    /// <summary>
    /// Extract all procDeclaration objects from case file
    /// </summary>
    public class ProcedureDeclarationExtractor
    {
        #region Private memebers

        XElement m_ProcDeclarationsRootElement = null;
        DbConnectionConfigExtractor m_ConnConfigExtractor;
        List<ProcedureDeclaration> m_ListProcDeclaration = new List<ProcedureDeclaration>();

        #endregion

        public ProcedureDeclarationExtractor(XElement procDeclarationsRootElement, DbConnectionConfigExtractor connectionConfigExtractor)
        {
            if (procDeclarationsRootElement != null)
            {
                if (connectionConfigExtractor == null)
                    throw new ArgumentNullException("connectionConfigExtractor");
            }
            m_ProcDeclarationsRootElement = procDeclarationsRootElement;
            m_ConnConfigExtractor = connectionConfigExtractor;
            Extract();
        }

        #region Private methods

        /// <summary>
        /// check whether alias of procDeclaration is non-empty and unique
        /// </summary>
        /// <param name="list"></param>
        private void ValidateAlias(IEnumerable<ProcedureDeclaration> list)
        {
            var listNames = new HashSet<string>();
            foreach (var item in list)
            {
                if (item.Alias.Length == 0)
                    throw new ArgumentException("ProcedureDeclaration.Alias cannot be an empty string.");

                //check whether each of Alias is unique
                if (false == listNames.Add(item.Alias))
                    throw new DuplicateNameException(string.Format("The Alias {0} is already present, please rename with another string.", item.Alias));
            }
        }


        private void Extract()
        {
            m_ListProcDeclaration.Clear();
            if (m_ProcDeclarationsRootElement == null)
                return; 

            var q = from procSection in m_ProcDeclarationsRootElement.Elements("procDeclaration")
                    let procName = procSection.Attribute("name").Value.Trim()
                    let aliasAttribute = procSection.Attribute("alias")
                    let namespaceAttribute = procSection.Attribute("namespace")
                    let argsRootElmt = procSection.Element("arguments")
                    let remarkElmt=procSection.Element("remark")
                    let dbConnection = procSection.Attribute("dbConnection").Value.Trim()
                    let dbConnConfig = m_ConnConfigExtractor[dbConnection]
                    let argsExtractor = new StatementDeclarationArgsExtractor(argsRootElmt, dbConnConfig.ProviderName, dbConnConfig.ConnectionString, false)
                    orderby procName
                    select new ProcedureDeclaration
                    {
                        ProcedureName = procName,
                        Alias = aliasAttribute.Value.Trim(),
                        Namespace = namespaceAttribute == null ? string.Empty : namespaceAttribute.Value.Trim(),
                        ListArgument = argsExtractor.ListArgument.ToList(),
                        ConnectionString = dbConnConfig.ConnectionString,
                        ConnectionProviderName = dbConnConfig.ProviderName,
                        Remark = (remarkElmt==null)?string.Empty :remarkElmt.Value.ToString() 
                    };

            m_ListProcDeclaration.AddRange(q);

            //validate the procedure alias unique
            ValidateAlias(m_ListProcDeclaration);
        }

        #endregion


        #region Public methods/properties

        public ReadOnlyCollection<ProcedureDeclaration> ListProcDeclaration
        {
            get
            {
                return new ReadOnlyCollection<ProcedureDeclaration>(m_ListProcDeclaration);
            }
        }

        public ProcedureDeclaration this[string procedureAlias]
        {
            get
            { 
                if (string.IsNullOrEmpty(procedureAlias))
                    throw new ArgumentException("procedureAlias must be assigned.");

                string upperName = procedureAlias.Trim().ToUpper();
                if (upperName.Length == 0)
                    throw new ArgumentException("procedureAlias cannot be an empty string.");


                foreach (ProcedureDeclaration item in m_ListProcDeclaration)
                {
                    if (item.Alias.ToUpper() == upperName)
                        return item;
                }

                string msgFmt = "Failed to find one procDeclaration which name is '{0}'.";
                string msg = string.Format(msgFmt, procedureAlias);
                throw new DbSharpException(msg);
            }
        }

        #endregion


    }
}
